Repository navigation
Mac-first completion: platform crate, AArch64 tiers, emit and runtime conformance, review fixes - #229
Open
rabbitson87 wants to merge 334 commits into
Open
rabbitson87 wants to merge 334 commits into
rabbitson87 wants to merge 334 commits into
Conversation
Contributor
Review of
|
Top-level `var` and function names lived in module cells, so `globalThis.x` could not see them and `asyncHelpers.js`, which looks for `$DONE` on the global object, could not run. Whether a file is a Script cannot be read from the file: Node runs the same bytes as a CommonJS module, where those names must stay local. So the host says. `compile_script_file` sets `LowerOptions::script_entry` for the entry file, which keeps its `var` and function names out of module cells, hoists each `var` onto the global object without disturbing one that exists, and binds `this` to the global object even in strict code. `let`, `const` and `class` stay off the global object. Knowing the goal also makes `import`, `export` and `import.meta` in a Script real early errors, which could not be decided from the tree. The test262 lane uses this for every non-module case; nothing else changes.
`let {} = undefined` did nothing, because only reading a property could fail
and an empty pattern reads none. With a computed key, the key also ran before
the failure. Binding and assignment patterns now require an object-coercible
value before anything else, as the specification does.
A generator ran nothing until its first `next`, so a default that throws, or a pattern given undefined, failed later than the call that caused it, or not at all when the generator was never advanced. The specification binds parameters during the call. The compiler now ends the prologue of a generator with non-simple parameters with a `Suspend` and sets `binds_at_call`, so a generator whose parameters cannot run code stays byte-identical. A call runs such a generator to that point. Its first `next` then resumes the suspended point, and `return` and `throw` arrive before any `try` in the body has started, as they would at the start. An async generator stops there without touching its empty request queue. The bytecode size pin and the decoder's unknown-bit case move with the flag.
The parser reads any `import.x` as a meta property, as `tsc` does, so
`import.UNKNOWN('a')` ran and `new import.defer('a')` was accepted. The early
pass now reads the word: `import.defer` and `import.source` exist only as the
callee of a call with a specifier and options, and nothing else but `meta`
exists. `new.target` and `import.meta` cannot contain escapes, and `new` cannot
reach `import()` through a member chain.
The parser built an await expression wherever `await` could start one, so `await(null)` in a plain function awaited instead of calling, and `await instanceof f` failed to parse. TypeScript keeps that reading so it can report misuse. In JavaScript, `await` outside an async function is now a name. At the top level of a file the goal is unsettled, so module syntax decides: without any, `await` stays a name unless what follows could not continue an expression, which is how Node decides the same file.
These all parsed and ran, and each is one small static rule: reserved words spelled with escapes, legacy octal in strict code and in templates, shorthand that only a pattern allows, a comma after a rest element, `const` and `using` without a value, `using` in patterns and case clauses, `return` outside a function, mixing `??` with `&&` or `||`, a template after `?.`, logical assignment to a call, a parenthesized pattern, a label on a function in an `if`, a private name not followed by `in`, `await` where only a name can be, and `await` or `yield` naming a function that binds them. Across every test262 variant this rejects 183 more invalid files and no valid one.
The loader stopped the whole program at the first import it could not resolve, so a check run saw one error at most, and 3,386 TypeScript conformance rows never reached the checker. `tsc` reports TS2307 for each one and keeps going. `ProgramLoader::tolerating_unresolved_imports` does that for a run that only checks. The loader's rules are the runtime's, so a package the runtime refuses still gets `tsc`'s own resolver, which reads `types` and `main` where the runtime needs `exports`. What neither finds becomes an edge the frontend reports, unless a `declare module` satisfies it. A run that executes keeps failing at load, because it cannot start with a module missing.
`eval` was absent, which failed every test or program that named it. With a host script compiler it now runs a string as a classic script against the global object and returns its completion value, and returns anything else unchanged. It shares its compile path with `Function`. A host with no script compiler still has no `eval`, so the name reads as undeclared there rather than failing when called. A classic script now also hoists its `var` names and binds `this` the way an entry Script does, because `eval` and `vm` both need that. Every call behaves as an indirect one for now: `eval` cannot see the locals of its caller, which needs scope access that lowering does not provide yet. Such code fails with a ReferenceError, so nothing silently reads the wrong binding unless a global has the same name.
`$262` did not exist, so every test that detached a buffer or read the host's global object failed for a reason that had nothing to do with the engine. The lane now defines it before the harness, with the attributes `INTERPRETING.md` requires. `detachArrayBuffer` goes through a real `ArrayBuffer.prototype.transfer`, and `evalScript` through real `eval`. Each captures what it uses, because tests replace builtins. `createRealm` and `agent` stay absent: nothing here can back them yet, and a test of a missing facility must fail rather than pass against an inert hook.
`String.prototype.toString` and `valueOf` and `Boolean.prototype.toString` were never installed, so `'a'.toString()` found `Object.prototype.toString` and returned `[object String]`. All three now return the receiver's own primitive and refuse any other receiver.
Big::set_bit resized its limb vector to word + 1 unconditionally. div_rem sets quotient bits from the top down, so each lower bit truncated the limbs already written, and every quotient of 2^32 or more lost its high limbs. Number.prototype.toPrecision then never returned for ordinary values at precision of about 15 and above: rounded_significand saw too few digits, lowered the exponent, saw too many, and oscillated forever. toFixed and toExponential reach the same division through exact_round_scaled. The loop sits inside a builtin, so no deadline in the interpreter loop can interrupt it. One test262 file that calls toPrecision(16) was enough to hang a whole directory run. The regression test's expected digits were computed with Python's decimal module, independently of this code.
A substitution was joined with `+`, which applies `ToPrimitive` with the
default hint, so `${o}` called `valueOf` before `toString`. It is also why
`${x}` threw for every Temporal object, whose `valueOf` must throw. The new
`UnaryOp::ToString` applies the conversion the specification names before the
join, so a Symbol throws too.
Code reaching the compiler at runtime only got the parser's checks, so
`eval('"use strict"; with (a) {}')` compiled and `Function` accepted strict-mode
violations. A classic script now goes through the same pass as a file.
Function name inference did not exist: `var f = function () {}` left `f.name`
empty, and a named function expression lost even its own name. Anonymous
functions, arrows and classes now take the name of the variable, assignment
target, default's binding, literal object key or static field they directly
initialize. Parentheses are transparent, and `(0, function () {})` and
`(x) = function () {}` stay anonymous, as the specification requires. A class
keeps a static `name` member of its own.
`[x = f()] = []` also bound through the element's whole pattern, so a default
that produced undefined ran twice. It now binds the target.
`named_class_constructor_carries_source_name_metadata` asserted that
`const C = class {}` stays unnamed, which `language/statements/const/
fn-name-class.js` contradicts. That assertion now covers `(0, class {})`, which
keeps its original intent, and a new one covers the binding. Computed keys and
instance fields are not named yet.
`builtins/date.rs` and `builtins/json.rs` were restored by a recovery commit but no module declares them: `builtins/mod.rs` compiles `date_full.rs` and `json_edge.rs`. Remove both and repoint the two `bigint.rs` doc comments that named `json.rs` at the module that does the work.
Reconnect `builtins/atomics.rs` to the module tree, reduced for now to the SharedArrayBuffer half: the constructor, `byteLength`, `maxByteLength`, `growable`, `grow`, `slice`, and species. TypedArray and DataView already view `SharedBlock` through `ViewBuffer`, so they gain shared buffers unchanged. `grow` charges the delta through the existing helper. `slice` shares the species lookup with ArrayBuffer. A non-object `options` argument means a fixed-length buffer, and a failed allocation is a RangeError rather than a fatal error. The `SharedArrayBuffer waits for atomics install` assertion pinned the state this replaces, so it now asserts the global exists.
`Machine::is_object` excluded every primitive heap entry except Symbol and PrivateName, so a Symbol passed its call sites as an object. Reflect and Proxy accepted Symbols where they must throw, `Object(symbol)` did not box its argument, `JSON.stringify` serialized a Symbol it must skip, the "can be held weakly" branch in WeakRef and FinalizationRegistry was unreachable, and `built-ins/Symbol` and `built-ins/Map` hung outright. Measured per file over Symbol, WeakRef, WeakMap, WeakSet, FinalizationRegistry, Reflect, Proxy, Promise, Map, Set, JSON, Function, ArrayBuffer, DataView, SharedArrayBuffer, and Object: 34 fixed, none regressed. Symbol and Map hung before the change, so those two directories have no per-file comparison.
Replace the empty `Atomics` namespace with `add`, `and`, `compareExchange`, `exchange`, `isLockFree`, `load`, `or`, `pause`, `store`, `sub`, and `xor`. Each operation follows the specification's order: validate the view, coerce the index against the bounds taken before coercion, coerce the operands, then revalidate, because a coercion can run guest code that detaches or shrinks the buffer. `store` returns the coerced operand rather than the wrapped element. Writes to a shared block run under the block's lock, which every other access takes too, so a read-modify-write is indivisible. `integer_or_infinity` now returns +0 rather than -0 for a small negative fraction, as ToIntegerOrInfinity is defined to. `atomics_claims_no_methods` pinned the empty namespace this replaces and cannot hold once Atomics has methods, so it now pins the exact method set instead.
The well-known symbol was never installed, so `{ [Symbol.toPrimitive]() {} }`
created a property named "undefined" and every coercion ignored the method. In
`Atomics.wait` that turned a timeout of `false` into NaN, which is by definition
an unbounded wait, and the test blocked forever.
Install the symbol and give ToPrimitive its first step: read the method, call it
with the "string", "number", or "default" hint, and reject an object result or a
non-callable method. `null` and `undefined` fall through to the ordinary methods.
`Date.prototype[Symbol.toPrimitive]` and `Symbol.prototype[Symbol.toPrimitive]`
are not added; `Date` keeps its existing default-hint handling.
Measured per file over Symbol, Date, Math, BigInt, String, JSON, and the
addition, equality, relational, template, and computed-name language tests:
43 fixed, none regressed.
`+object` went through the non-observable number conversion, so it never called `valueOf` or `Symbol.toPrimitive` and yielded NaN. Use the observable ToNumber, which also makes `+proxy` and `+object` with a throwing method behave. `Number(object)`, `-object`, and `~object` share the same fault and are not changed here.
Give each shared block a real WaiterList: a FIFO queue per byte position, guarded by its own lock. `wait` compares the value and enqueues inside that critical section, so a writer that stores and then notifies either changes what the comparison sees or finds the waiter queued. It then suspends the agent on a condition variable until woken or its timeout passes, returning "not-equal", "ok", or "timed-out". A timeout that finds its waiter already taken lost a race to a notifier, and reports "ok". There is one agent, so nothing can notify a suspended `wait` yet; the unit tests stand a thread in for the second agent to prove the wake path. A wait with no timeout therefore blocks forever, as the specification requires, and only the machine's cancellation token ends it. `Limits::agent_can_suspend` is the agent's [[CanBlock]] and defaults to true. `notify` on unshared memory validates its arguments and returns 0.
`waitAsync` queues a real waiter and returns a pending promise. `notify` resolves it on the spot, which is what NotifyWaiter does for a waiter of the current agent, so its reactions run at the next microtask checkpoint. A finite timeout arms a host timer that settles the promise with "timed-out"; `notify` disarms it. A timeout of zero, or a value that does not match, answers without a promise. The block's list holds only an id. The machine owns the record with the promise and traces it, because a Value inside a block would be invisible to the collector. An untimed wait arms nothing, so it does not keep the event loop alive: with one agent nothing can ever notify it, and the program ends with the promise pending. `TimerProvider` delays are `u32`, so a longer timeout re-arms until all of it has passed rather than firing early. `clearTimeout` ignores a bare number that names one of these timers, since a script has no business cancelling them. On a host without timers a timed wait fails with `TimerProviderFailure`; an untimed one works.
`ExternalInbox` was built for a `waitAsync` that settled from a helper thread, and nothing ever produced a job for it. NotifyWaiter resolves a same-agent waiter directly and timeouts run on host timers, so only a second agent could use it, and there is none. Remove the queue, its drain, and the two `allow(dead_code)`s that hid it. An agent implementation can add a queue shaped by what it actually needs.
… block `INTERPRETING.md` says these cases run only when [[CanBlock]] is false, but the oracle parsed the flag and ignored it, so they ran on a blocking agent and could only fail. Carry the flag through the plan into `Limits::agent_can_suspend`, which keeps its default of true for every other case.
`own_descriptor` fell through to "no such property" for ArrayBuffer, SharedArrayBuffer, and DataView, so a property set on one was invisible to `Object.getOwnPropertyDescriptor` and `Object.keys` even though `hasOwnProperty` and reads found it. Promise and the generator entries fall through the same way and are not changed here.
date_until_year_month_day expected 13 days from 2020-02-25 to 2020-03-10. That cannot hold: the test contradicts its own round-trip assertions, which require start + d == end, and 2019-02-25 + 1Y + 13D is 2020-03-09. February 2020 has 29 days, so the distance is 14. The arithmetic was checked independently of this library with Python's datetime: date(2020, 3, 10) - date(2020, 2, 25) is 14 days. No implementation could satisfy the test as written. This is a deliberate, authorized exception to the rule against changing expected values. It changes only the two literals. The inputs and the round-trip assertions are unchanged, because they are the oracle.
These expectations pinned behaviour of code that had never been run
against test262. Each one below is directly contradicted by the named
file in the pinned suite, so the suite wins. This commit changes only
tests; the behaviour that satisfies them follows separately.
This is a deliberate, authorized exception to the rule against changing
expected values, limited to assertions a specific test262 file
contradicts.
duration_parse_fractional_seconds
old: PT0.5H => (hours, milliseconds) == (0, 1_800_000)
new: PT0.5H => (hours, minutes, milliseconds) == (0, 30, 0)
test/built-ins/Temporal/Duration/from/
argument-string-fractional-units-rounding-mode.js
PT1.03125H must give 1h 1m 52s 500ms: a fraction spills through
minutes and seconds.
instant_parse_annotations
old: Instant::parse("...Z[!u-ca=japanese]").is_err()
new: Instant::parse("...Z[!u-ca=japanese]").is_ok()
test/built-ins/Temporal/Instant/from/
argument-string-calendar-annotation.js
"critical flag has no effect": [!u-ca=hebrew] and [!u-ca=discord]
must be accepted, because an Instant has no calendar.
date_parse_extended_years_round_trip
old: PlainDate::parse("2000-05-15T12:30:00Z") yields 2000-05-15
new: that string is an error; the date-portion assertion it carried
now uses "2000-05-15T12:30:00", without the designator
test/built-ins/Temporal/PlainDate/from/
argument-string-with-utc-designator.js
"2019-10-01T09:00:00Z" must throw RangeError.
cross_type_year_month_and_month_day
old: ym.format() == "2024-02-01"
new: ym.format() == "2024-02"
year_month_arithmetic_and_difference
old: later.format() == "2022-01-01"
new: later.format() == "2022-01"
old: PlainYearMonth::parse("2020-11").unwrap().format() == "2020-11-01"
new: PlainYearMonth::parse("2020-11").unwrap().format() == "2020-11"
test/built-ins/Temporal/PlainYearMonth/prototype/toString/
calendarname-auto.js
An ISO year-month serializes as "2000-05", with no reference day.
run_process clears the child environment and forwards only SystemRoot and windir on Windows. A Windows process finds its temporary directory through TEMP and TMP, so std::env::temp_dir() in a child was C:\Windows. The CLI builds its fallback cache root under that directory and failed with "access denied" before it compiled anything. Forward TEMP and TMP next to SystemRoot. They stay out of NORMALIZED_ENV, which evidence records byte for byte. Correct the comment that said no forwarded variable reaches the guest program.
When a corpus child outlives its timeout the harness kills it and waits. Unix then reports the signal and no exit code. Windows reports the 1 that TerminateProcess sets, so a timed-out run looked like a program that exited with status 1, and a timeout compared differently on the two platforms. A child the harness killed has no exit code of its own: record none when the run timed out. Unix already behaved this way.
The project tests wrote roots such as /workspace and /project, which have
no drive and so are not absolute on Windows. ProjectRoot::new rejected
them, and about a hundred tests failed before they reached their
assertions.
Add a test-only helper, project::test_paths::abs, that returns the path
unchanged where it is already absolute and puts it on the C: drive on
Windows. Fixture roots, inputs and expected paths all go through it, so
no expected value changed. Paths that sit inside JSON text use abs_json,
which escapes the backslashes, and the trace tests derive the drive that
upstream traces print ("c:") instead of writing it out.
The output planning tests used /workspace as the project root. A path without a drive is not absolute on Windows, so plan_outputs rejected the root before it reached the assertions. The context test set TMPDIR, which Windows does not read; it reads TMP and TEMP. Build the fixture roots with a small helper that keeps the Unix text and adds the C: drive on Windows, derive the expected paths from it, and set the variable the platform reads. No expected value changed.
The lane tests spawned /bin/sleep and /usr/bin/true, which Windows does not have (os error 3), and bound the snapshot to /canonical/ts-suite, which has no drive and so is not an absolute root there. Pick the worker per platform: sleep and true on Unix, PING.EXE with three echoes and cmd.exe /C exit 0 from the system directory on Windows. Build the snapshot root and the worker directory with a helper that keeps the Unix text and adds the C: drive on Windows. The assertions are unchanged.
The module doc said every canonical path in the product goes through canonicalize. Several checks in bamts-verification still call fs::canonicalize, on purpose: each compares a path only with other fs::canonicalize results, so the two spellings never meet. Limit the promise to the compiler, the CLI and the bamts facade, name the kind of code that may keep the platform call, and state the rule for the rest: a path compared with compiler output must use this function.
…bute Windows flushes a file only through a handle with write access, and a file with the read-only attribute refuses that handle (access denied in both open modes). sync_tree flushes every extracted file, so an archive entry that tar unpacked with the attribute made source materialization fail on Windows. The Unix flush through a read-only handle already covered the same case, and its test is limited to Unix. On Windows, when the write handle is refused and the file has the attribute, lift it for the flush and put it back whether or not the flush worked. The helper that clears the flag exists only on Windows, where it touches nothing but the attribute. The Unix code is the same as before.
The comments said a directory cannot be opened for writing on Windows. That is not true: on NTFS a handle opened with FILE_FLAG_BACKUP_SEMANTICS for writing accepts a flush (checked on a Windows 11 host). What is true is that std cannot open a directory there and that this code does not open one that way. Say that, and say what the skip costs: NTFS journals the rename, which is not a promise that the rename is on disk after a power loss. The code is unchanged.
The first two lines of the GotEntry doc comment described needs_got_entry, so the alias read as "whether the relocation reads its symbol through a GOT entry" and the function had no doc at all. Move those lines to the function. No code changes.
bamts-platform had a Linux and a macOS backend and answered Unsupported on Windows, so the host-JIT region tests, the peak-RSS telemetry and the peak-memory window could not run there. Add a Windows backend with no new dependency. The few Win32 calls it needs are declared by hand, and each unsafe block states why it is sound. Struct sizes are asserted at build time. process_regions walks VirtualQueryEx and reports committed regions as rwxp strings: copy-on-write counts as writable, guard pages and PAGE_NOACCESS as ---p. File-backed regions name their file as a DOS path, mapped from the NT device with QueryDosDeviceW, and UNC shares as \\server. peak_memory_bytes reports the peak working set, and PeakWindow samples the working set every millisecond, as the macOS window does. A process that has exited answers ProcessGone at once, even while its parent still holds a handle: the host keeps answering reads until the last handle closes, which would hang a poller that joins before the child is dropped. A live process that refuses a read gets NoAddressSpace, and a refusal on a live process for any other reason is a read error. The pure parts, the permission letters and the NT-to-DOS mapping, are built on every host so their tests run on Linux and macOS too.
The map sampler and the peak poller tests started /bin/cat and /bin/sh, which Windows does not have, and the late-mapping test compiled a POSIX C program with cc. With the Windows platform backend in place they can run, once they start a child that exists there. Pick the child per platform. sort.exe from the system directory stays alive until its input closes, as cat does; ping with two echoes outlives a few polls, as sleep does. The late-mapping child is a small Rust program that calls VirtualAlloc and VirtualProtect, built with the rustc of this toolchain; the Unix C child is unchanged and only moved into a helper. The sampler compares the executable with region path names, which are plain paths, so the controlled AOT child and the tests canonicalize it with the shared helper instead of fs::canonicalize, whose verbatim spelling would never match. The assertions are unchanged.
The Windows backend answered only process regions and peak memory. Every other reader still said Unsupported, so the stage-0 and JIT benchmark harnesses could not record the conditions of the host they ran on. machine_facts reads the real Windows version with RtlGetVersion and the update revision from the registry, the CPU model from the registry, and sockets and cores per socket from GetLogicalProcessorInformationEx. The microcode comes from the four-byte Update Revision value, the only form checked against a host; any other form is UNAVAILABLE rather than a guess. swap_total_kib sums EnumPageFiles. online_cpu_list and cpu_affinity count across processor groups. The NUMA nodes come from the same topology records, and thread_placement formats them like the Linux reader. A Windows host has no cpufreq governor and no numactl policy, so those facts are UNAVAILABLE, as on macOS. Every value was compared with CIM and the registry on the development host. The record parsers and the CPU list formatting build on every host, so their tests run on Linux and macOS too.
…msvc The MSVC linker takes no libraries from a Rust staticlib. Linking the embedded runtime archive needs the system libraries that its crates link against, kernel32, ntdll, advapi32 and the C runtime among them, and only rustc knows that list. A fixed list would go stale the next time a dependency adds a link attribute: the platform crate's registry reader already added advapi32. When the archive is assembled for an msvc target, ask rustc for --print=native-static-libs, parse its note, and export the list as BAMTS_NODE_NATIVE_LIBS. A missing note on msvc fails the build rather than yielding an empty list. Every other target runs rustc with the same arguments as before and exports an empty value.
The AOT path linked with "cc object archive -o executable", which is not an MSVC command line, so no native executable could be built on Windows. The emitted object is a valid COFF x64 object (checked with dumpbin: only REL32 and ADDR64 relocations, the entry and the program descriptor defined, the runtime helpers undefined), the C main comes from the runtime archive, and a link.exe run produces an executable whose output matches node's. Add windows_link, which finds the linker, builds the LIB search path and builds the command line. The linker is BAMTS_LINKER if set, where a wrong value is an error and not a reason to look further; else the first link.exe on PATH that prints the Microsoft banner, which skips the GNU link that Git for Windows ships; else the newest Visual Studio with the C++ tools, found with vswhere at its fixed Installer place. LIB comes from the context when it has one, and otherwise from the toolset of the linker and the newest complete Windows SDK. The child environment is the context, minus the LINK and _LINK_ variables that link.exe reads as options. A cleared environment still works: vswhere gets ProgramData, and the system folders the context does not name are looked for on the drive of SystemRoot. Standard output is kept in LinkFailed, because link.exe prints its errors there. The runtime archive is linked with the libraries from build.rs and /BREPRO, so equal inputs give equal bytes. The driver gets a small Linker seam. The Unix code that ran cc moves into it unchanged, and on msvc the seam is windows_link.
tsc_type_error_emits_with_status_2 looked for the emitted executable at the source path with its extension removed. The driver names an executable with the platform suffix, so on Windows the file is "name.exe" and the test could not find it. Use std::env::consts::EXE_EXTENSION, which is empty on Unix, so the path the test checks there is the same as before.
Drop the test-or-Windows gates that repeat the gate of the module, build Read and Malformed errors through read_failed and malformed, name the registry sources once, and share the UTF-16 conversion. No behavior changes.
…ped files process_regions asked the host for all 26 drive letters on every call. GetLogicalDrives lists the letters in use, so a host with four drives is asked four times. A host that lists none is still asked for every letter.
The build script matched the target name twice. The match on the staticlib build is a statement again, as it was before the native libraries were recorded.
Share the case-insensitive environment lookup with the Windows temporary directory, start the linker probes through one context command, search PATH through one iterator, and keep the runtime library list inside windows_link. The driver takes the program name from the command it runs.
The lane, perf and perf_stage0 tests each built the System32 path by hand.
…ng the toolset toolset_of took the first four bytes of the directory above the linker. A name such as a Korean directory has a character that spans byte 4, so the slice panicked instead of finding no toolset. Also say in the BAMTS_LINKER doc that a linker outside a Visual Studio layout finds its libraries through LIB.
The walk must end with an error when a whole header names a size below eight bytes, or a size of zero would never advance. The old test cut the header short and never reached that check. A failed wait is now named after the wait call, not after OpenProcess.
…l links on msvc A named program wins over Visual Studio, a Microsoft linker on PATH wins over Visual Studio, a cancelled token stops each stage of the search, and two links of the same source give the same bytes because of /BREPRO.
…ead of failing the facts
…during the name lookup
…in the compiler worker load_bound_compiler_snapshot hashed pin, index and ledger (180 MB) twice per case: once to check the lane binding and once for CompilerSnapshot::metadata_digest, which nothing reads. Drop the unread field and its hash. The binding check still authenticates the same bytes. One worker call drops from about 590 ms to about 470 ms.
… from one process With BAMTS_LANE_WORKER_SERVE set, ts_lane_worker prints a ready line, then answers one request path per line of standard input and prints a done line with the request ID after each response is written. It authenticates the snapshot of a binding once and keeps it for the later requests of that binding. With BAMTS_LANE_WORKER_REQUEST set it still answers the one request, unchanged. A failure that ends a one-request worker ends a serving worker the same way, so the parent can record the same row. The parent does not use this mode yet.
…per shard Each compiler case used to start ts_lane_worker anew, and each start read, hashed and parsed the 180 MB snapshot metadata before a compile of about 13 ms. A conformance shard of about 16,700 cases needed hours and hit the 45-minute job limit on every CI run. ServingExecutor keeps one serving worker for many requests. Each request keeps its own request file, response file, binding check in the parent and deadline. A worker that exits or misses a deadline gives its request the observation ProcessExecutor gives such a worker, and the next request starts a new worker. The suite uses it for the compiler runner only; the other lane workers still answer one request per process. ProcessObservation::Answered marks a response from a worker that keeps running and derives the row the same way as a clean exit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Two stretches of the completion plan, done on an Apple M3 Max (
aarch64-apple-darwin), plus a review round. The first made the workspace build and pass on macOS and added local CI and sweep tools. The second makes emitted JavaScript and the runtime behave correctly. The review round fixes what two reviewers and an independent read-only audit found in the second. 291 commits againstmain. The hosted workflows are unchanged.This is not the finished product. The compiler and package roots are still open, and nothing here claims otherwise.
Please merge without squashing.
AGENTS.mdasks for a bisectable series; see Process notes for the commits that fall short of it.Results at
4154686eb2eec1; no newer receipt4db845cjust cieb2eec14154686The two test262 numbers use different denominators: the receipt counts the catalog's applicable rows, the runner counts files. No JIT, AOT or full TypeScript receipt has been taken at this commit; that is the
full-baselinecard and it stays open.Hosted CI at
4154686:Quality,Corpus,Formal,Preflight,Windows native boundaryand CodeQL pass (Qualityfails onmain). The fourConformance receipt shardjobs andConformance receipt accountingfail: each shard runs into its 45-minute job timeout (.github/workflows/ci.yml:277) and is cancelled, and accounting fails because no shard delivered.main's latest run (4ba4408) fails the same way, so this predates the branch. The workflow is unchanged here, so the timeout stays.What changed in the second stretch
var/letlower correctly.superthrough a home object, read-write keys converted once, for-of targets.TZ=UTC;.types/.symbolsheaders use the catalog path; target cells are format-neutral across seven targets.What changed in the review round
recountnow reruns a sample of passing rows (default 500) so--fail-on-regressioncan fire;doctorchecks each tool alone; perf harnesses read only the host facts they use; the status report refuses its own sidecar's path.toSorted(cmp),Object.groupBy,f(...gen()),promise.thenand many others no longer die with an uncatchableheap slot does not exist. Loops that use each callback result once (reduce,forEach,mapand their like) keep only the latest 32.modulekind emits (es2020,es2022,preserve,node16/nodenext, per file by extension andpackage.json); CommonJS imports stay in source order; function exports hoist above the requires; re-exported imports are live getters; exported locals stay current on every write;export =ismodule.exports; string export names are quoted from their decoded text.export *, and only forexport { x }andexport default x.verbatimModuleSyntax, JavaScript files,@jsx,emitDecoratorMetadataandreactNamespacekeep every import.newthrows;static namedefines instead of throwing; field initializers resolve in the class's scope; an arrow'ssuper()shares the constructor's state; a destructuring target is evaluated before its value; athisparameter takes no argument.Math.abs(new Number(-1)),'ab'.repeat(0), catch-pattern defaults, an empty ES module keepingexport {};, and a lost reply whenshutdownraced a request in the API server.Verified
just cipassed at4154686;catalog,completionanddiagnostics regenerate --checkpass.#[ignore],todo!,unimplemented!or#[allow]across the second stretch and the review round..github, lockfiles (second stretch onward),rust-toolchain.tomland every generated file are unchanged by this range, exceptverification/manifest.lock.json, which1352e7bregenerated for the target cell change.heap slot ... does not existwhen its root is removed.tsc7.0.2 output or Node.Not verified
driver.rsstill callscc ... -o, and the platform crate has no Windows implementation at all: its machine-fact, process-region and peak-memory readers returnUnsupportedthere (there is nowindows.rs; the fallbacks are incrates/bamts-platform/src/lib.rs). Expect failures; they are the input for the Windows tasks.work/stdlibcode. The seven conflicted files were reviewed, and a read-only audit covered the twelve files both sides touched. The rest of the merge was not audited.Known defects and limits
Known test262 regressions against
eda5413(11 files, down from 12):language/arguments-object/10.6-13-a-{2,3}.js,language/eval-code/{direct,indirect}/non-definable-global-{function,generator}.js,built-ins/ThrowTypeError/unique-per-realm-non-simple.js,built-ins/Date/prototype/setFullYear/15.9.5.40_1.js, and three TemporaltoLocaleString/ignore-timezone.jsfiles.Open and not fixed:
f.lengthcounts default parameters;module: preservewithexport =;.mtsimport x = require();moduleDetection: forceandimport.metamiss theexport {}marker; CommonJS source maps skip require lines andnewLine: crlfleaves the prelude LF; a trailing comment after an import is lost;__importDefault/__importStarare not emitted; array destructuring never closes iterators;class A {} interface A {}and<NS.Comp />raise false errors;@jsxpragmas are ignored by the checker; shebangs are dropped; CommonJSimport()is not rewritten;class extends nullthrows at definition; a builtin that collects very large callback results keeps them all rooted until it returns, so it can reach the heap limit where Node would not (String.prototype.replacewith a huge result is one).Product limits: local-time
Datethrows unlessTZisUTCor a POSIX form, which includes the normal unset case; locale data isen/en-USonly andchinese,dangiand islamic calendars are missing;bamts file.tswrites a native executable and no.js, while atsconfig.jsonproject writes JavaScript;argumentsis still an Array; direct eval cannot see caller locals; Annex B block functions are missing.Decisions to review
Test expectations changed. The plan's rule is to never change an expected value to make something pass. These changed, with reasons, and still need review:
7d321c4,f13e726,494c9fb,a8a641c(atomics_claims_no_methods, changed by a worker without prior approval),2a6cb50,f8cdfaa.tsc:0183c1f,65e026b,5c09826,5861c67,cacb3d7,e0521c6,190379b,1b67908, andcommonjs_export_specifiers_read_what_the_name_became(this series had added that expectation ind086b56;tsc7.0.2 emits the getter).fe8e661, removed with the dead code they covered.host_conditions_come_from_the_platform_crategained a branch for a Linux host with no cpufreq; its assertions are unchanged.Held, with tests unchanged. Each pinned test conflicts with test262 or
tsc, so the old behavior stays until someone decides:date_parse_extended_years_round_trip(blocks thejapanesecalendar, 144 files, ready on a local branch)commonjs_class_and_variable_exports_receive_void_0_preamblenamespace_reports_unlowered_diagnostic_in_javascriptiterator_rejects_non_array_receiver__defineGetter__and__defineSetter__tests inannex_b.rsruntime_invalid_date_propagates_and_iso_throwslocale_compare_uses_observable_utf16_lexical_signspromise_all_closes_an_abrupt_iterator_and_keeps_the_original_throwinstalled_methods_have_standard_descriptors_names_and_lengthsstackaccessor testsclass B extends A { x = 1 }runs an inherited setter (Node defines); pinned byinstance_field_init_after_superlowertestsexport {};is not added whenmoduleis unset; pinned by two emitter testssuper();tscaccepts itsuper()refuses before re-running the parentReview comments not changed, with reasons: the tests without a Linux gate (
jit.rsregion tests,run_with_telemetry_reports_peak_rss) stay ungated because gating them would be the hiding#[cfg]the plan forbids, they fail on Windows only because the platform readers are unimplemented there, and that failure is the input for the Windows task; the contract leaves that own files not yet written (windows_link.rsand two toolchain records) are real deliverables of open cards, and their ledgers now say which card writes each; no CI job runsci-npm-releasebecause it needs all five host archives and hosted workflows are deliberately unchanged, sofive-host-installruns it by hand.Process notes
af1a13c,3dda270and3cfa106each hold more than one mechanism, and49da830holds two that share their helpers.work/stdlibmerge,cargo check --all-targetspasses on the first 75 of 76 commits, and per-commit unit tests ran only on the first 48 plus six others, sogit bisectthrough that range may be less reliable than elsewhere.driver.rsandfilesystem.rstests predate this work.First stretch, as written at
1948470What this is
The first stretch of the completion plan, done on an Apple M3 Max (
aarch64-apple-darwin): make the workspace build and pass there, move OS reads behind one crate, make in-process AOT/JIT work on AArch64, add local CI recipes, and add the tools that turn a conformance sweep into a short list of fixable causes. 55 commits, 57 files. The hosted workflows are unchanged.Please merge without squashing.
AGENTS.mdasks for a bisectable series with one mechanism per commit, and the commits are built that way.What changed
b372182…e885f01,5614d90…232c221,5ff64cdbamts-platformcrate. Machine facts, CPU facts, process regions and peak memory are read there for Linux (x86_64 and AArch64) and macOS. Nothing outside it reads/procor/sys.portability-checkenforces that.883387d,b27b14c,ab6d3dd,f5655e2,343433fCALL26as the reader reports it, and flushes the instruction cache. JIT code is checked against the host process map on every host.7fefcbc,b494022x86_64-pc-windows-msvc. This is a compile result only.7ecdff8,ee90d06,7fdeb9b,d96f07a,7d9951b,9b4def8,f2d1342c7bfc36,e92e173,9f3dc78just ciruns fmt, check, clippy, workspace tests, npm tests, the corpus check, cross-targetcargo checkand the portability check on one machine.ci-npmskips the two groups that need all five host archives;ci-npm-releaseruns everything.e9450c8FROZEN_LEAF_COUNTis now 114, with a test. The nine ledgers are in the same commit because validation rejects either half alone.710e645,4eb2d11,5fd123c,42254fc,b42c264,af4b98e,4a95f44,3c331d3,1948470suite families,suite diff,suite recount,suite status, the matchingjustrecipes,just gate-status, and structural-mismatch messages that name the first differing line. Receipt states are unchanged: a shard rerun before and after the message change gave identical states on all 1,044 rows.a0310fc,8c592e6,91b0b04npm/test/bamti-native-runner.test.mjs, which tested functions275e12ahad already removed; adds the single-worker change-window procedure; ignores the local plan ledger.Verified
On macOS arm64 at
1948470,just ciprintedLOCAL CI PASS:cargo fmt --all --check,cargo check --workspace --lockedandcargo clippy --workspace --all-targets --locked -- -D warningspass.cargo test --workspace --locked: 4,714 passed, 0 failed, 12 ignored.ci-npm.just xcheckcompiles for x86_64 macOS, x86_64 Linux, AArch64 Linux and x86_64 Windows.portability-checkpasses.Not verified
driver.rsstill callscc ... -o, which is not an MSVC command line, and the platform crate has no Windows implementation (its readers returnUnsupported). Expect Windows test failures. Those failures are the input for the next Windows task.x86_64-apple-darwinnpm archive in this series was cross-built from arm64, so it carries an empty runtime archive; it passes the install-metadata tests and nothing else.ledger rebuild --checkrejects all 248,828 obligations. It fails the same way on9f3dc78, the commit before the contract change, so this PR does not cause it. It stays failing until the measurement baseline is restored.completion regenerate --checkand the catalog check pass.Decisions to review
bamti-native-runner.test.mjsis reversible withgit revert a0310fc.verification/completion-program.tomlby hand, becausecompletion regeneratevalidates and rewrites that file but does not derive leaves. After the edit it wrote back identical bytes. The reasoning is ine9450c8.<facet>/<slug>of the diagnostic text. It is defined in one function,family_nameinsweep_report.rs.Known leftovers
typesandsymbolsfacets still fall into two largetextfamilies (174 and 168 rows) because their record lines have no line kinds yet.javascriptobservable for project cases, and 10 rows fail with "cannot resolve ". Both are compiler work for later.api_server::tests::shutdown_answers_then_stops_the_loopfailed once in earlier runs and did not recur in three full runs. It is still unexplained.#[ignore]d tests are unchanged.